package com.iussoft.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.apache.tools.DateUtils;
import com.apache.tools.MD5Utils;
import com.apache.tools.StrUtil;
import com.apache.tools.UUIDUtil;
import com.google.common.collect.Maps;
import com.iussoft.manager.RedisManager;
import com.iussoft.tools.LoggerUtil;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import com.google.common.util.concurrent.RateLimiter;

import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;

/**
 * @description: 全局过滤器父类
 * @author: Hou Dayu
 * @date: Created in 2020/3/11
 */
public abstract class SuperGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    @Qualifier("redisManager")
    protected RedisManager redisManager;

    //protected static Map<String, RateLimiter> map = Maps.newConcurrentMap();
    protected static Map<String, Map<String,LongAdder>> map = Maps.newConcurrentMap();


    protected Mono<Void> globalProtocol(ServerWebExchange exchange, GatewayFilterChain chain,JSONObject route,long startTime ){
        ServerHttpRequest request = exchange.getRequest();
        String useIp = getIpAddress(request);
        String uri =request.getURI().toString();
        String reqUrl = "";
        //String ifBackup = route.getString("ifBackup");//是否开启备份
        String traceId = UUIDUtil.getPrimaryKey();
        if(uri.indexOf("?")==-1){
            reqUrl = uri+"?traceId="+ traceId+"&useIP="+useIp;// +"&useIP="+useIp+"&ifBackup="+ifBackup;
        }else{
            reqUrl = uri+"&traceId="+ traceId+"&useIP="+useIp;// +"&useIP="+useIp+"&ifBackup="+ifBackup;
        }
        boolean isOpenLog = "1".equals(route.getString("ifOpenLog"));//是否开启打印日志
        String whiteStr = String.valueOf(redisManager.getlObjCache(useIp,10));
        StringBuilder normalMsg = new StringBuilder();
        normalMsg.append("traceId = "+traceId);
        normalMsg.append("; requestUrl = "+uri);
        normalMsg.append("; requestIp = "+useIp);
        normalMsg.append("; exeSys = gateway; requestTime = "+ DateUtils.Now.fmt_yyyyMMdd_HHmmss());
        if(StrUtil.isNull(whiteStr)){//不在配置的黑白名单中
            if(isOpenLog) {
                normalMsg.append("; total = " + (System.currentTimeMillis() - startTime));
                normalMsg.append("; infoType = warning; errorMessage = 您不在授访范围内，此次访问已被禁止，请您先申请权限; level = ERROR");
                LoggerUtil.warn(normalMsg.toString());
            }
            return getResponse(exchange.getResponse(),"您不在授访范围内，此次访问已被禁止，请您先申请权限");
        } else {
            JSONObject jobj = JSON.parseObject(whiteStr);
            if("1".equals(jobj.getString("infoType"))){//黑名单
                if(isOpenLog) {
                    normalMsg.append("; total = " + (System.currentTimeMillis() - startTime));
                    normalMsg.append("; infoType = warning; errorMessage = 您已被拉入黑名单，此次访问已被禁止");
                    LoggerUtil.warn(normalMsg.toString());
                }
                return getResponse(exchange.getResponse(),"您已被拉入黑名单，此次访问已被禁止");
            }
        }
        String reqBody="";
        int fileNum = uri.indexOf("/file/data/");
        int imoprtNum = uri.indexOf("/receive/import/");
        if(fileNum > -1 || imoprtNum > -1) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        DataBufferUtils.release(dataBuffer);
                        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                            DataBufferUtils.retain(buffer);
                            return Mono.just(buffer);
                        });

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        DataBufferUtils.release(dataBuffer);
                        ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
                        return ServerRequest.create(mutatedExchange, HandlerStrategies.withDefaults().messageReaders())
                                .bodyToMono(byte[].class)
                                .then(chain.filter(mutatedExchange));
                    });
        }

        URI ex = UriComponentsBuilder.fromUriString(reqUrl).build(true).toUri();
        RecorderServerHttpRequestDecorator requestDecorator = null;
        boolean ifOpenCache = "1".equals(route.getString("ifOpenCache"));//是否开启应答缓存
        if(ifOpenCache){//!isOpenLog || ifOpenCache
            requestDecorator = new RecorderServerHttpRequestDecorator(request.mutate().uri(ex).build());
            AtomicReference<String> requestBody = requestDecorator.getRequestBody();
            //reqBody = requestBody.get().replaceAll("\n", "").replaceAll("\r", "");
            //normalMsg.append("; requestBody = ").append(reqBody);
            StringBuilder sr = new StringBuilder();
            sr.append(uri);
            sr.append(useIp);
            sr.append(requestBody.get().replaceAll("\n", "").replaceAll("\r", ""));
            //sr.append(reqBody);
            String reqCacheKey = MD5Utils.MD5(sr.toString());
            if(null == redisManager.getlObjCache(reqCacheKey,10)) {
                redisManager.createObjCache(reqCacheKey, "T", 3, 10);
            } else {
                if(isOpenLog) {
                    normalMsg.append("; errorMessage = 重复请求了");
                    LoggerUtil.info(normalMsg.toString());
                }
                ServerHttpResponse response = exchange.getResponse();
                Response json = new Response("302", "请求重复请求了", "请求重复请求了");
                DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JSON.toJSONString(json).getBytes());
                return response.writeWith(Mono.just(bodyDataBuffer));
            }
        }
        normalMsg.append("; requestBody = 验证耗时"+(System.currentTimeMillis() - startTime)+"m");
        if(requestDecorator == null){
            if (isOpenLog) {
                return chain.filter(exchange.mutate().request(request.mutate().uri(ex).build()).build()).then(Mono.fromRunnable(() -> {
                    normalMsg.append("; infoType = info; total = " + (System.currentTimeMillis() - startTime));
                    LoggerUtil.warn(normalMsg.toString());
                }));
                //return sendServer(exchange,chain,requestDecorator,normalMsg.toString(),startTime);
            } else {
                return chain.filter(exchange.mutate().request(request.mutate().uri(ex).build()).build());
            }
        }else {
            if (isOpenLog) {
                return chain.filter(exchange.mutate().request(requestDecorator).build()).then(Mono.fromRunnable(() -> {
                    normalMsg.append("; infoType = info; total = " + (System.currentTimeMillis() - startTime));
                    LoggerUtil.warn(normalMsg.toString());
                }));
                //return sendServer(exchange,chain,requestDecorator,normalMsg.toString(),startTime);
            } else {
                return chain.filter(exchange.mutate().request(requestDecorator).build());
            }
        }
    }

    /**
     * 发送请求
     */
    private Mono<Void> sendServer(ServerWebExchange exchange, GatewayFilterChain chain,RecorderServerHttpRequestDecorator requestDecorator,String msg,long startTime){
        StringBuilder normalMsg = new StringBuilder(msg);
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        DataBufferUtils.release(join);// 释放掉内存
                        String responseResult = new String(content, Charset.forName("UTF-8"));
                        normalMsg.append("; total = "+(System.currentTimeMillis()-startTime));
                        normalMsg.append("; infoType = info; responseBody = ").append(responseResult);
                        LoggerUtil.warn(normalMsg.toString());
                        byte[] newRs = responseResult.getBytes(Charset.forName("UTF-8"));
                        originalResponse.getHeaders().setContentLength(newRs.length);//如果不重新设置长度则收不到消息。
                        return bufferFactory.wrap(newRs);
                    }));
                }
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().request(requestDecorator).response(decoratedResponse).build());
    }

    protected Mono<Void> getResponse(ServerHttpResponse sResponse, String message){
        JSONObject json = new JSONObject();
        json.put("errorCode", "403");
        json.put("errorMessage",message);
        json.put("sendTime",System.currentTimeMillis());
        sResponse.getHeaders().set("Content-Type", "application/json; charset=UTF-8");
        DataBuffer bodyDataBuffer = sResponse.bufferFactory().wrap(json.toJSONString().getBytes());
        return sResponse.writeWith(Mono.just(bodyDataBuffer));
    }

    protected JSONObject getGatewayRoute(ServerWebExchange exchange){
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        String key = route.getId();
        String routeStr = String.valueOf(redisManager.getlObjCache(key,10));
        if(StrUtil.isNotNull(routeStr)){
            JSONObject json = JSON.parseObject(routeStr);
            return json;
        }
        return null;
    }

    protected String getIpAddress(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        return ip;
    }

    @Override
    public int getOrder() {
        return -200;
    }
}
